/**
 * json转换器
 */
function sJSONConvertor() {
    var parser = new DOMParser();
    
    /**
     * 数值计算保留的小数位
     */
    const PRV_DEC_PLACES = 2;

    /**
     * canvas画布
     */
    var canvas = null;

    /**
     * 字体大小本地像素转换比
     */
    var ratioFONT = 1;

    /**
     * px单位本地像素转换比
     */
    var ratioPX = 1;

    /**
     * pt单位本地像素转换比
     */
    var ratioPT = 1;

    /**
     * cm单位本地像素转换比
     */
    var ratioCM = 1;

    /**
     * 设置外部单位和本地单位的转换比
     * @param {*} opt
     * 
     */
    this.setScale = function(opt){
        if(!opt) return;

        ratioFONT = opt.scale_font;
        ratioPX = opt.scale_px;
        ratioPT = opt.scale_pt;
        ratioCM = opt.scale_cm;
    }

    /**
     * 解析html并转换成Json结构
     * @param {*} html html字符串
     * @param {*} type 区域类型 body,head
     * @returns 
     */
    this.parseHTML = function (html, type) {
        var docx = parser.parseFromString(html, "text/html");
        var element = docx.querySelector(type);

        return element2Json(element);
    }

    /**
     * 元素转JSON
     * @param {*} element 
     * @returns 
     */
    var element2Json = function (element) {
        const json = {};

        json.tagName = element.tagName.toLowerCase();

        if (element.hasAttributes()) {
            json.attributes = { style: {}, props: {} };
            for (var key of element.style) {
                key = styleToHumpName(key);
                json.attributes.style[key] = element.style[key];
            }

            for(var attr of element.attributes){
                if(attr.name == "style") continue;
                json.attributes.props[attr.name] = attr.value;
            }
        }

        if (element.hasChildNodes()) {
            json.children = [];
            for (const child of element.childNodes) {
                if (child.nodeType === Node.ELEMENT_NODE) {
                    json.children.push(element2Json(child));
                } else if (child.nodeType === Node.TEXT_NODE) {
                    let textContent = trim(child.textContent);
                    if(textContent.length > 0){
                        if(hasLineBreak(child.textContent)){
                            let splitChars = child.textContent.split("\n");
                            for(let i in splitChars){
                                textContent = trim(splitChars[i]);
                                if(textContent.length == 0) continue;
                                json.children.push({
                                    tagName: "div",
                                    text: textContent
                                });
                            }
                        }else{
                            json.children.push({
                                text: textContent
                            })
                        }
                    }
                }
            }
        }

        return json;
    }

    /**
     * 去掉文本中的回车换行符
     * @param {*} text 
     * @returns 
     */
    var trim = function(text){
        //去掉回车换行字符
        var txt = text ? text.replace(/[\r\n]+/g, "") : "";
        return txt;
    }

    /**
     * 是否包含换行符
     * @param {*} text 
     * @returns 
     */
    var hasLineBreak = function(text){
        return text.indexOf("\n") > -1;
    }

    /**
     * 样式分隔线命名转驼峰命名
     * @param {*} name 
     * @returns 
     */
    var styleToHumpName = function(name){
        return name.replace(/[-]([a-z])/g, function(c){return c.replace("-","").toUpperCase()})
    }

    /**
     * 解析文本文件并转成json结构
     * @param {*} txt 
     */
    this.parseTxt = function(txt){
        var txts = [];

        var text = txt;
        var ptext = null;
        var len = 2;

        var index = txt.indexOf("\r\n");
        if(index < 0){
            index = txt.indexOf("\n");
            len = 1;
        }

        while(index > -1){
            ptext = text.substring(0, index);

            txts.push({
                _sType: "sText",
                innerText: ptext
            });

            txts.push({
                _sType: "sParagraph"
            })

            text = text.substring(index + len);
            index = text.indexOf("\r\n");

            if(index < 0){
                index = text.indexOf("\n");
                len = 1;
            }else{
                len = 2;
            }
        }

        if(text){
            txts.push({
                _sType: "sText",
                innerText: text
            });

            if(index > - 1){
                txts.push({
                    _sType: "sParagraph"
                })
            }
        }

        return {
            _sType: "root",
            children: txts
        };
    }

    /**
     * 解析html-json并转成本地json
     * @param {*} htmlJson 
     * @returns 本地json
     */
    this.convertToLocal = function (htmlJson, localJson, style) {
        localJson = localJson 
            ? localJson 
            : { 
                _sType: "root", 
                children: [] 
            };

        var nodeStyle = null;
        var nodeProps = null;
        var flg = true;
        var tagNameL = null;

        for (const node of htmlJson.children) {
            if(node.tagName){
                tagNameL = node.tagName.toLowerCase();
                if(tagNameL != "img" && tagNameL != "input"){
                    if(canIgnore(node)) continue;
                }
            }
            
            nodeProps = node.attributes && node.attributes.props;
            nodeProps = nodeProps ? nodeProps : {};
            nodeStyle = {
                ...style, 
                ...node.attributes && node.attributes.style,
                ...extractStyleFromNode(node)
            };
            
            flg = true;

            wrap(node, localJson, nodeStyle, true);

            if(node.tagName == "table"){
                convertToLocalTable.call(this, node, localJson, nodeStyle);
                continue;
            }else if(node.tagName == "img"){
                let image = new sImage();
                parseImgSrc(nodeProps.src 
                    ? nodeProps.src 
                    : nodeProps["data-original"], 
                    (img, success)=>{
                        if(success){
                            if(img.width){
                                image.style.width = img.width * ratioPX;
                                image.style.height = img.height * ratioPX;
                            }else if(node.attributes && 
                                node.attributes.style && 
                                node.attributes.style.width){
                                image.style.width = parseFloat(node.attributes.style.width) * ratioPX;
                                image.style.height = parseFloat(node.attributes.style.height) * ratioPX;
                            }
   
                            image.src = img;
                        }
 
                        image.dispatchEvent("load");
                    }
                );

                var style = {
                    width: nodeProps.width != null 
                        ? parseFloat(nodeProps.width) * ratioPX : 100,
                    height: nodeProps.height != null 
                        ? parseFloat(nodeProps.height) * ratioPX : 100,
                    ...filterStyleForLocal(nodeStyle, "sLayer"),
                };

                localJson.children.push({
                    _sType: "sLayer",
                    children: [image],
                    style: style
                });
            }else if(node.tagName == "label"){
                if(nodeProps.presenttype){
                    let layer = {
                        _sType: "sLayer",
                        children: [],
                        style: filterStyleForLocal(nodeStyle, "sLayer"),
                        presentType: nodeProps.presenttype
                    };
                    localJson.children.push(layer);

                    if(node.children){
                        this.convertToLocal(node, layer);
                    }
                }
            }else if(node.tagName == "input"){
                if(nodeProps.type == "checkbox" || nodeProps.type == "radio"){
                    let sType = nodeProps.type == "checkbox" ? "sCheckBox" : "sRadio";
                    let children = localJson.children;

                    if(localJson._sType != "sLayer"){
                        let layer = {
                            _sType: "sLayer",
                            children: [],
                            style: {
                                width: 80,
                                height: sType == "sCheckBox" 
                                    ? ssDict.sDefaultProps.CHECKBOX_HEIGHT 
                                    : ssDict.sDefaultProps.RADIO_HEIGHT
                            },
                            presentType: nodeProps.presenttype
                        };
                        children.push(layer);
                        children = layer.children;
                    }

                    let box = {
                        _sType: sType,
                        children: [],
                        style: filterStyleForLocal(nodeStyle, sType),
                        presentType: nodeProps.presenttype
                    };
                    children.push(box);
                }
            }else if(node.tagName == "span"){
                if(nodeProps.type == "sInputBegin" || nodeProps.type == "sInputEnd"){
                    if(nodeProps.list && typeof(nodeProps.list) == "string"){
                        nodeProps.list = JSON.parse(nodeProps.list);
                    }

                    localJson.children.push({
                        _sType: nodeProps.type,
                        style: filterStyleForLocal(nodeStyle, nodeProps.type),
                        attributes: nodeProps
                    });
                    
                    flg = false;
                }
            }

            if(flg){
                if (node.children && !nodeProps.presenttype) {
                    this.convertToLocal(node, localJson, nodeStyle);
                }
    
                if (node.text) {
                    var localStyle = filterStyleForLocal(nodeStyle, "sText");
                    if(htmlJson.tagName == "sup" || htmlJson.tagName == "sub"){
                        localStyle[htmlJson.tagName] = true;
                    }
                    
                    localJson.children.push({
                        _sType: "sText",
                        innerText: replaceTextForLocal(node.text),
                        style: localStyle
                    })
                }else if(nodeProps.placeholder){
                    var localStyle = filterStyleForLocal(nodeStyle, "sText");
                    
                    localJson.children.push({
                        _sType: "sText",
                        innerText: "",
                        placeholder: nodeProps.placeholder,
                        placeholderColor: nodeProps.placeholdercolor,
                        style: localStyle
                    })
                }
            }

            wrap(node, localJson, nodeStyle);
        }

        return localJson;
    }

    /**
     * 是否可忽略该节点
     * @param {*} node 
     */
    var canIgnore = function(node){
        let ignore = !node.children;

        if(!ignore){
            return false;
        }else{
            ignore = !node.text || !node.text.trim();
        }

        if(!ignore){
            return false;
        }else{
            ignore = !node.text || !node.text.trim();
        }

        if(!ignore){
            return false;
        }else{
            ignore = !node.attributes ||
                !node.attributes.props || 
                node.attributes.props.placeholder === undefined
        }

        if(!ignore){
            return false;
        }

        return true;
    }

    /**
     * html中特殊字符转义成本地字符
     */
    var replaceTextForLocal = function(text){
        text = text.replace(/[ ]/g, " ");
        text = text.replace("&lt", "<");
        text = text.replace("&gt", ">");

        return text;
    }

    /**
     * 解析图片来源
     * @param {*} src 
     * @param {*} callback
     */
    var parseImgSrc = function(src, callback){
        if(src.indexOf("http") > -1 || 
           src.indexOf("https") > -1){
            try{
                loadHttpFileToBase64(src, (base64)=>{
                    let image = new Image();
                    image.src = base64;
                    image.onload = ()=>{
                        callback(image, true);
                    }
                });
            }catch(e){
                console.error(e);
            }
        }else if(src.indexOf("data:image") > -1){
            let image = new Image();
            image.src = src;
            image.onload = ()=>{
                callback(image, true);
            }
        }else if(src.indexOf("file") > -1){
            callback();
        }else{
            callback(src, true);
        }
    }

    /**
     * 将图片文件转成base64 
     * @param {*} image 
     * @param {*} callback 
     */
    var loadHttpFileToBase64 = function(src, callback) {
        fetch(src)
            .then(response => response.blob())
            .then(blob => {
                const reader = new FileReader();
                reader.readAsDataURL(blob);
                reader.onloadend = () => {
                    const base64 = reader.result;
                    callback(base64);
                }
            });
    }

    /**
     * 是否要换行
     * @param {*} node 
     * @param {*} localJson 
     * @param {*} style 
     * @param {*} before
     * @returns 
     */
    var wrap = function(node, localJson, style, before){
        if (needParagraph(node)) {
            if (localJson.children.length == 0) {
                return false;
            }

            var lastChild = localJson.children;
            lastChild = lastChild[lastChild.length - 1];

            //当前内容之前的内容不是段落和换行，则插入换行
            if(before){
                if(lastChild._sType != "sParagraph" &&
                   lastChild._sType != "sBreakline"){
                    localJson.children.push({
                        _sType: "sParagraph",
                        style: filterStyleForLocal(style, "sParagraph")
                    });
    
                    return true;
                }
            }else{
                if(isBr(node) || !node.text){
                    localJson.children.push({
                        _sType: "sText",
                        innerText: ""
                    });
                }

                localJson.children.push({
                    _sType: "sParagraph",
                    style: filterStyleForLocal(style, "sParagraph")
                });
            }
        }

        return false;
    }

    /**
     * 将html-json-table转成本地json-table
     * @param {*} table 
     * @param {*} localJson 
     * @param {*} style 
     */
    var convertToLocalTable = function(table, localJson, style){
        var tableJson = {
            _sType: "sTable",
            style: {
                ...filterStyleForLocal(style, "sTable"),
                display: "block"
            },
            columns: [],
            rows: []
        };
        
        localJson.children.push(tableJson);

        var child = null;
        var tr = null;
        var trInd = null;

        for(var i in table.children){
            child = table.children[i];
            
            if(!child.tagName || 
              (child.tagName != "thead" && 
               child.tagName != "tbody")){ 
                continue;
            }

            trInd = 0;

            for(var j = 0; j < child.children.length; j++){
                tr = child.children[j];
                if(!tr.tagName || tr.tagName != "tr" || !tr.children || tr.children.length == 0) continue;

                convertToLocalRow.call(this, tr, tableJson, 
                    tr.attributes && tr.attributes.style, trInd++);
            }
        }
    }

    /**
     * 将html-json-tr转成本地json-table-row
     * @param {*} tr html-json表格行
     * @param {*} tableJson 表格local-json对象
     * @param {*} style 样式
     * @param {*} rowIndex 行序号
     */
    var convertToLocalRow = function(tr, tableJson, style, rowIndex){
        var rowJson = {
            _sType: "sTableRow",
            style: filterStyleForLocal(style, "sTableRow"),
            cells: []
        };

        tableJson.rows.push(rowJson);

        var td = null;
        var tdStyle = null;
        var attr = null;
        var width = 0;
        var colIndex = 0;
        var colspan = null;
        var rowspan = null;
        var column = null;
        var columns = tableJson.columns;

        for(var i in tr.children){
            td = tr.children[i];
            if(!td.tagName || (td.tagName != "td" && td.tagName != "th")) continue;
            
            attr = td.attributes ? td.attributes : {};
            tdStyle = filterStyleForLocal(attr.style, "sTableColumn");
            colspan = attr.props && attr.props.colspan ? parseInt(attr.props.colspan) : 1;
            rowspan = attr.props && attr.props.rowspan ? parseInt(attr.props.rowspan) : 1;
            tdStyle.width = tdStyle.width != null ? tdStyle.width : 100;
            width = tdStyle.width ? parseFloat(tdStyle.width) : width;

            var start = colIndex;
            var end = colIndex + colspan - 1;

            //计算生成表格列
            for(var j = 0; j < colspan; j++){
                colIndex = start + j;

                //当前存在列，且该列上存在合并单元格
                while(columns[colIndex]){
                    var tp = columns[colIndex].__temp__;
                    if(isSpanRange(tp.rowRange, tp.colRange, rowIndex, colIndex)){
                        colIndex = tp.colRange.end + 1;

                        start = colIndex;
                        end = colIndex + colspan - 1;

                        //对于被合并的单元格，要补充生成
                        for(var m = tp.colRange.start; m <= tp.colRange.end; m++){
                            complementLocalCell(td, rowJson, tdStyle, attr.props);
                        }
                    }else{
                        break;
                    }
                }

                if(!columns[colIndex]){
                    columns[colIndex] = {
                        _sType: "sTableColumn",
                        style: j == 0 ? {...tdStyle} : {...tdStyle, width: 0},
                        __temp__: {
                            width: tdStyle.width,
                            widthFixed: colspan == 1,
                            colRange: {
                                start: start,
                                end: end
                            },
                            rowRange: {
                                start: rowIndex,
                                end: rowIndex + rowspan - 1
                            }
                        }
                    };
                }else if(colspan == 1){
                    column = columns[colIndex];

                    var temp = column.__temp__;

                    if(!temp.widthFixed){
                        column.style.width = width;
                        temp.widthFixed = true;

                        if(temp.colRange.end > temp.colRange.start){
                            var count = 0;
                            var fixedWidth = 0;

                            for(var k = temp.colRange.start; k <= temp.colRange.end; k++){
                                if(!columns[k].__temp__.widthFixed){
                                    count++;
                                }else{
                                    fixedWidth += columns[k].style.width;
                                }
                            }

                            var avgWidth = parseFloat(
                                ((temp.width - fixedWidth) / count).toFixed(PRV_DEC_PLACES));

                            for(var k = temp.colRange.start; k <= temp.colRange.end; k++){
                                if(!columns[k].__temp__.widthFixed){
                                    columns[k].style.width = avgWidth;
                                }
                            }
                        }
                    }

                    column.__temp__.colRange = {
                        start: start, end: end
                    };
                    column.__temp__.rowRange = {
                        start: rowIndex,
                        end: rowIndex + rowspan - 1
                    };
                }else{
                    column = tableJson.columns[colIndex];

                    column.__temp__.colRange = {
                        start: start, end: end
                    };
                    column.__temp__.rowRange = {
                        start: rowIndex,
                        end: rowIndex + rowspan - 1
                    };
                }

                if(j == 0){
                    convertToLocalCell.call(this, {...td}, rowJson, {...tdStyle}, {...attr.props});
                }else{
                    complementLocalCell.call(this, td, rowJson, tdStyle, attr.props);
                }
            }

            colIndex++;
        }

        //如果单元格少于列数，则补充单元格
        if(rowJson.cells.length < tableJson.columns.length){
            var len = tableJson.columns.length - rowJson.cells.length;
            for(var i = 0; i < len; i++){
                convertToLocalCell.call(this, td, rowJson, {display: "none"}, null);
            }
        }
    }

    /**
     * 补充单元格(被合并的单元格)
     * @param {*} td 
     * @param {*} rowJson 
     * @param {*} tdStyle 
     * @param {*} tdProps 
     */
    var complementLocalCell = function(td, rowJson, tdStyle, tdProps){
        td = {...td, children: null};

        var style = {...tdStyle, display: "none"};
        var props = {...tdProps};

        delete props.colspan;
        delete props.rowspan;

        //生成当前行单元格
        convertToLocalCell.call(this, td, rowJson, style, props);
    }

    /**
     * 判断当前单元格是否在合并单元格的范围内
     * @param {*} rowRange 行合并范围
     * @param {*} colRange 列合并范围
     * @param {*} colIndex 当前列序号
     * @param {*} rowIndex 当前行序号
     */
    var isSpanRange = function(rowRange, colRange, rowIndex, colIndex){
        return (colRange.end > colRange.start ||
                rowRange.end > rowRange.start) && 
                colIndex >= colRange.start && 
                colIndex <= colRange.end && 
                rowIndex >= rowRange.start && 
                rowIndex <= rowRange.end;
    }

    /**
     * 将html-json-td转成本地json-table-cell
     * @param {*} td 当前列
     * @param {*} tableJson 表格json对象 
     * @param {*} style 样式
     * @param {*} props 属性
     */
    var convertToLocalCell = function(td, rowJson, style, props){
        const cellType = "sTableCell";

        var cellJson = {
            _sType: cellType,
            children: []
        };

        if(style){
            cellJson.style = filterStyleForLocal(style, cellType);
        }

        if(props){
            for(var name in props){
                if(!ssDict.sPropsSupport[name] || 
                    ssDict.sPropsSupport[name].elementTypes.indexOf(cellType) < 0) continue;
                if(name == "colspan" || name == "rowspan"){
                    cellJson[name] = parseInt(props[name]);
                }else{
                    cellJson[name];
                }
            }
        }

        rowJson.cells.push(cellJson);

        if(td && td.children){
            this.convertToLocal(td, cellJson, td.attributes && td.attributes.style);
        }

        //单元格默认插入一个文本和段落
        if(cellJson.children.length == 0 && 
            cellJson.style.display != "none"){
            cellJson.children.push({
                _sType: "sText",
                innerText: ""
            });
            cellJson.children.push({
                _sType: "sParagraph",
            });
        }
    }

    /**
     * 本地元素是否是段落元素
     * @param {*} sNode 
     * @returns 
     */
    var isParagraphInLocal = function (sNode) {
        return sNode && sNode._sType === "sParagraph";
    }

    /**
     * 标签对应样式
     */
    var tag2Style = {
        "i": {name: "fontStyle", value: "italic"},
        "b": {name: "fontWeight", value: "bold"},
        "strong": {name: "fontWeight", value: "bold"},
        "u": {name: "textDecoration", value: "underline"},
    };

    /**
     * 从特定标签中抽取样式
     * @param {*} node 
     */
    var extractStyleFromNode = function(node) {
        var ts = tag2Style[node.tagName];
        if(!ts) return null;

        var style = {};
        style[ts.name] = ts.value;

        return style;
    }

    /**
     * 需要新增段落
     * @param {*} node 
     * @returns 
     */
    var needParagraph = function (node) {
        return isBlock(node) ||
            isParagraph(node) ||
            isTable(node) ||
            (isDiv(node) && isDefault(node)) ||
            (isLi(node) && isDefault(node)) ||
            isBr(node);
    };

    /**
     * 是块状元素
     * @param {*} node 
     * @returns 
     */
    var isBlock = function (node) {
        return node.attributes &&
            node.attributes.style &&
            node.attributes.style.display === "block";
    }

    /**
     * 是默认display的元素
     */
    var isDefault = function (node) {
        return !node.attributes ||
            !node.attributes.style ||
            !node.attributes.style.display;
    }

    /**
     * 是段落元素
     * @param {*} node 
     */
    var isParagraph = function (node) {
        return node.tagName === "p";
    }

    /**
     * 是div元素
     * @param {*} node 
     * @returns 
     */
    var isDiv = function (node) {
        return node.tagName === "div";
    }

    /**
     * 是li元素
     * @param {*} node 
     */
    var isLi = function (node) {
        return node.tagName === "li";
    }

    /**
     * 是br元素
     * @param {*} node 
     */
    var isBr = function (node) {
        return node.tagName === "br";
    }

    /**
     * 是表格
     * @param {*} node 
     */
    var isTable = function(node){
        return node.tagName === "table";
    }

    /**
     * 从html样式中过滤出本地支持的样式
     * @param {*} style 
     * @param {*} sType 
     */
    var filterStyleForLocal = function (style, sType) {
        if (!style) return null;

        var localStyle = {};

        for (var i in style) {
            if (!ssDict.sStylesSupport[i]) continue;
            if (ssDict.sStylesSupport[i].elementTypes.indexOf(sType) < 0) continue;

            toLocalStyle(localStyle, i, style[i]);
        }

        return localStyle;
    }

    /**
     * 将Html样式转成本地样式
     * @param {*} localStyle
     * @param {*} key 
     * @param {*} value 
     */
    var toLocalStyle = function(localStyle, key, value){
        var val = value;

        if(key == "fontSize"){
            if(isEndWith(value, "px", true)){
                val = parseFloat((parseFloat(value) / ratioFONT).toFixed(PRV_DEC_PLACES));
            }else if(isEndWith(value, "pt", true)){
                val = parseFloat((parseFloat(value)).toFixed(PRV_DEC_PLACES));
            }
        }else if(typeof(value) == "string"){
            if(isEndWith(value, "px", true)){
                val = parseFloat((parseFloat(value) * ratioPX).toFixed(PRV_DEC_PLACES));
            }else if(isEndWith(value, "pt", true)){
                val = parseFloat((parseFloat(value) * ratioPT).toFixed(PRV_DEC_PLACES));
            }else if(isEndWith(value, "cm", true)){
                val = parseFloat((parseFloat(value) * ratioCM).toFixed(PRV_DEC_PLACES))
            }else if(isEndWith(value, "em", true) || isEndWith(value, "rem", true)){
                val = parseFloat((parseFloat(value) * 9).toFixed(PRV_DEC_PLACES))
            }
        }

        if(key == "lineHeight"){
            val += "px";
        }else if(key == "letterSpacing"){
            if(value == "normal"){
                val = 0;
            }
        }else if(key.indexOf("padding") == 0){
            var direction = key.substring(7);
            if(direction){
                localStyle.padding = localStyle.padding ? localStyle.padding : {};
                key = direction.toLowerCase();
                value = parseFloat(val);
            }
        }

        localStyle[key] = val;
    }

    /**
     * 判断字符串value是否以str字符结尾
     * @param {*} value 
     * @param {*} str 字符串
     * @param {*} ignoreCase true忽略大小写
     */
    var isEndWith = function(value, str, ignoreCase){
        var flg = false;
        var index = null;
        var charArr = [str];

        if(ignoreCase){
            charArr.push(str.toLowerCase());
            charArr.push(str.toUpperCase());
        }

        for(var i in charArr){
            index = value.indexOf(charArr[i]);
            flg = index > -1 && index == value.length - str.length;

            if(flg) return flg;
        }

        return flg;
    }

    /**
     * 将本地json转成html
     * @param {*} elementList 本地元素列表
     * @param {*} isReturnJson true 返回html-json
     * @param {*} parent 父级子对象集合
     * @param {*} option 可选项
     */
    this.convertToHTML = function(elementList, isReturnJson, pchildren, option){
        option = option ? option : {};

        var htmls = pchildren ? pchildren : [];
        var parent = null;
        var paragraph = null;
        var table = null;
        var element = null;
        var el = null;

        for(var i = elementList.length - 1; i >= 0; i--){
            el = elementList[i];
            element = el.element ? el.element : el;
            parent = paragraph ? paragraph.children : htmls;

            if(element._sType == "sTable"){
                htmls.push(converToHtmlTable.call(this, element, true));
            }else if(element._sType == "sTableCell"){
                var prop = extractCellProp(el.offset.length > 0 
                    ? el.offset[0] 
                    : el.offset
                );

                if(!table || !table.id || table.ref != el.table){
                    if(table){
                        htmls.push(converToHtmlTable.call(this, table));
                    }

                    table = {
                        id: prop.id,
                        rows: [],
                        columns: [],
                        style: el.table.style,
                        props:{
                            cellspacing: 1,
                            border: 1
                        },
                        ref: el.table
                    };
                }
                
                if(table.ref == el.table){
                    if(table.columns.indexOf(el.table.getColumns(prop.colIndex)) < 0){
                        table.columns.push(el.table.getColumns(prop.colIndex));
                    }

                    if(!table.rows[prop.rowIndex]){
                        table.rows[prop.rowIndex] = {
                            cells: []
                        };
                    }

                    table.rows[prop.rowIndex].style = el.table.getRows(prop.rowIndex).style;
                    table.rows[prop.rowIndex].cells.push(element);
                }
            }else{
                if(table){
                    htmls.push(converToHtmlTable.call(this, table));
                    table = null;
                }

                if(element._sType == "sText"){
                    let hasPlaceholder = element.placeholder != null && element.placeholder.length > 0;
                    let hasInnerText = element.innerText != null && element.innerText.length > 0;
                    
                    let props = null;
                    let style = {
                        ...element.style, 
                        ...option.style,
                        display: "inline-block"
                    };

                    if(!hasInnerText){
                        if(hasPlaceholder){
                            props = {
                                placeholder: element.placeholder
                            };
                            
                            if(element.placeholderColor){
                                props.placeholderColor = element.placeholderColor;
                            }
                        }else{
                            //如果文本内容为空，呈现为html时，行元素高度会变成0
                            let rect = getElementRect(element);
                            style.height = rect.occupyHeight ? rect.occupyHeight : rect.height;
                        }
                    }

                    parent.push(buildLayoutTag(
                        "span", 
                        option.innerText 
                            ? option.innerText 
                            : element.innerText,
                        style,
                        props
                    ));
                }else if(element._sType == "sLayer"){
                    if(element.presentType && element.presentType != "image"){
                        let label = buildLayoutTag(
                            "label",
                            null,
                            {...option.style, ...element.style},
                            { presentType: element.presentType }
                        );
                        parent.push(label);
                        this.convertToHTML(element.children, true, label.children);
                    }else{
                        this.convertToHTML(element.children, true, parent, {style: element.style});
                    }
                }else if(element._sType == "sImage"){
                    parent.push(buildLayoutTag(
                        "img",
                        null,
                        {...element.style, ...option.style},
                        { src: typeof(element.src) == "object" 
                            ? element.src.src 
                            : element.src 
                        }
                    ));
                }else if(element._sType == "sCheckBox"){
                    if(element.children.length > 1){
                        this.convertToHTML([element.children[1]], true, parent);
                    }

                    let input = buildLayoutTag(
                        "input",
                        null,
                        {...option.style, ...element.style},
                        { type: "checkbox" }
                    );
                    parent.push(input);
                }else if(element._sType == "sRadio"){
                    if(element.children.length > 1){
                        this.convertToHTML([element.children[1]], true, parent);
                    }

                    let input = buildLayoutTag(
                        "input",
                        null,
                        {...option.style, ...element.style},
                        { type: "radio" }
                    );
                    parent.push(input);
                }
                else if(element._sType == "sBreakline"){
                }else if(element._sType == "sParagraph"){
                    paragraph = buildLayoutTag(
                        "p",
                        null,
                        {
                            ...element.style, 
                            margin: 0, 
                            display: "block"
                        }
                    );
                    htmls.push(paragraph);
                }else if(element._sType == "sInputBegin"){
                    parent.push(buildLayoutTag(
                        "span",
                        "[",
                        element.style,
                        { 
                            ...element.attributes,
                            type: element._sType
                        }
                    ));
                }else if(element._sType == "sInputEnd"){
                    parent.push(buildLayoutTag(
                        "span",
                        "]",
                        element.style,
                        { 
                            ...element.attributes,
                            type: element._sType
                        }
                    ));
                }
            }
        }

        if(table){
            htmls.push(converToHtmlTable.call(this, table));
            table = null;
        }

        return isReturnJson ? htmls : stringifyHtmlJson(htmls, true);
    }

    /**
     * 转换成html-json格式的表格
     * @param {*} table 表格
     * @param {*} reverse 反转单元格
     */
    var converToHtmlTable = function(table, reverse){
        var style = {...table.style};

        style.wordBreak = "break-all";
        style.wordWrap = "break-all";
        style.borderCollapse = "collapse";

        var tableTag = buildLayoutTag("table", 
            null, style, table.props, {notCloneStyle: true});
        var tbodyTag = buildLayoutTag("tbody", null, null);
        
        tableTag.children.push(tbodyTag);

        var rowTag = null;
        var colTag = null;
        var row = null;
        var width = 0;
        var cells = null;
        var columns = table.columns
            ? table.columns
            : table.getColumns();
        var rows = table.rows 
            ? table.rows 
            : table.getRows();

        for(var j in rows){
            row = rows[j];
            rowTag = buildLayoutTag("tr", null, row.style);
            tbodyTag.children.push(rowTag);

            cells = row.cells ? row.cells : row.getCells();

            forEach(cells, reverse, (cell, i)=>{
                if(cell == null) return;

                width = 0;

                for(var j = i; j < i + cell.colspan; j++){
                    width += parseFloat(columns[j].style.width);
                }

                colTag = buildLayoutTag("td", null, parseCellAlign({
                    ...cell.style, 
                    width: width,
                    "box-sizing": "border-box"
                }), {
                    rowspan: cell.rowspan,
                    colspan: cell.colspan
                }, { notCloneStyle: true });

                rowTag.children.push(colTag);

                if(cell.children && cell.children.length > 0){
                    colTag.children = this.convertToHTML(cell.children, true);
                }else if(cell.innerHTML){
                    colTag.innerHTML = cell.innerHTML;
                }
            });
        }

        tbodyTag.children.reverse();
        tableTag.children.reverse();

        return tableTag;
    }

    /**
     * 解析单元格内部排版位置
     * @param {*} style 
     * @returns 
     */
    var parseCellAlign = function(style){
        if(style.align){
            var align = style.align.replace(/([LCR])/g, ",$1");
            var arr = align.split(",");
            
            if(arr.length > 0){
                style.verticalAlign = arr[0];
                if(arr.length > 1){
                    style.textAlign = arr[1].toLowerCase();
                }
            }

            delete style.align;
        }

        return style;
    }

    /**
     * 根据偏移量生成ID
     * @param {*} offset 
     * @returns 
     */
    var extractCellProp = function(offset){
        var prop = {
            id: ""
        };

        for(var next = offset; next != null; next = next.next){
            if(next.next == null){
                prop.rowIndex = next.tableRowIndex;
                prop.colIndex = next.tableColIndex;
            }else{
                prop.id += (prop.id ? "/" : "") + next.pageIndex + "-" + next.rowIndex + "-" + next.elementIndex;
            }
        }
        return prop;
    }

    /**
     * 获取元素高度
     * @param {*} element 
     */
    var getElementRect = function(element){
        if(element.__boundRect){
            var r = element.__boundRect;
            r = r[r.length - 1];
            r = r[r.length - 1];

            return r;
        }

        return 0;
    }

    /**
     * 遍历列表
     * @param {*} list 
     * @param {*} reverse 
     * @param {*} callback 
     */
    var forEach = function(list, reverse, callback){
        if(reverse){
            for(var i = list.length - 1; i >= 0; i--){
                callback(list[i], i);
            }
        }else{
            for(var i = 0; i < list.length; i++){
                callback(list[i], i);
            }
        }
    }

    /**
     * 构造布局标签
     * @param {*} tag 标签名
     * @param {*} text 文本内容
     * @param {*} style 样式
     * @param {*} props 其他属性
     * @param {*} opt 可选项
     * @returns 
     */
    var buildLayoutTag = function(tagName, text, style, props, opt){
        var stl = null;
        var result = {
            tagName: tagName,
            children: []
        }

        if(!opt || !opt.notCloneStyle){
            style = {...style};
        }

        style = filterStyleForHTML(style, tagName);

        if(style && style.hasProp){
            delete style.hasProp;

            stl = {};

            for(var i in style){
                if(style[i] == null) continue;
                if(i == "fontSize"){
                    stl[styleToDividerName(i)] = parseFloat(style[i]) * ratioFONT + "px";
                }else{
                    stl[styleToDividerName(i)] = style[i];
                }
            }

            result.attributes = result.attributes ? result.attributes : {};
            result.attributes.style = stl;
        }

        if(props){
            result.attributes = result.attributes ? result.attributes : {};
            result.attributes.props = props;
        }

        if(text != null){
            result.text = replaceTextForHTML(text);
        }

        return result;
    }

    /**
     * 文本内容中特殊字符转义成html字符
     */
    var replaceTextForHTML = function(text){
        text = text.replace(/[\s]/g, "&nbsp;");
        text = text.replace("<", "&lt");
        text = text.replace(">", "&gt");

        return text;
    }

    /**
     * 过滤出html标签支持的样式
     * @param {*} style 
     * @param {*} tagName
     */
    var filterStyleForHTML = function(style, tagName){
        if(!style) return null;

        for(var i in style){
            if(style[i] == null || style[i].length == 0) continue;
            if(ssDict.sStylesHasPXUnit.indexOf(i) > -1){
                if(i == "padding"){
                    style.padding = localToHTMLPadding(style.padding);
                }else{
                    style[i] = localToHTMLPX(style[i]) + "px";
                }
            }

            style.hasProp = true;
        }

        if(!style.backgroundColor){
            style.backgroundColor = "transparent";
        }

        return style;
    }

    /**
     * 将本地padding转成Html格式的padding
     * @param {*} padding 
     * @returns 
     */
    var localToHTMLPadding = function(padding){
        if(typeof(padding) == "string"){
            padding = parsePoM(padding)
        }

        var htmlPadding = "";

        if(padding.top){
            htmlPadding += localToHTMLPX(padding.top) + "px ";
        }

        if(padding.right){
            htmlPadding += localToHTMLPX(padding.right) + "px ";
        }

        if(padding.bottom){
            htmlPadding += localToHTMLPX(padding.bottom) + "px ";
        }

        if(padding.left){
            htmlPadding += localToHTMLPX(padding.left) + "px";
        }

        return htmlPadding;
    }

    /**
     * 将padding或margin字符串解析为对象
     * @param {*} pom
     * @returns 
     */
    var parsePoM = function (pom) {
        var result = {
            top: 0,
            right: 0,
            bottom: 0,
            left: 0
        };

        if (!pom) return result;

        if(pom instanceof Object){
            for(var i in result){
                if(pom[i] !== undefined){
                    result[i] = parseFloat(pom[i]);
                }
            }
            return result;
        }

        pom = pom.toString();

        var arr = null;

        if (pom.indexOf(",") > -1) {
            arr = pom.split(",");
        }else if(pom.indexOf(" ") > -1) {
            arr = pom.split(" ");
        }

        if(arr){
            result.top = parseFloat(arr[0]);

            if (arr.length == 1) {
                result.right = result.top;
                result.bottom = result.top;
                result.left = result.top;
            }
            if (arr.length > 1) {
                result.right = parseFloat(arr[1]);
            }
            if (arr.length == 2) {
                result.bottom = parseFloat(arr[0]);
                result.left = parseFloat(arr[1]);
            }
            if (arr.length > 2) {
                result.bottom = parseFloat(arr[2]);
            }
            if (arr.length > 3) {
                result.left = parseFloat(arr[3]);
            }
        }

        return result;
    };

    /**
     * 将本地PX值转成htmlPX值
     * @param {*} key
     * @param {*} value 
     */
    var localToHTMLPX = function(value){
        return (parseFloat(value) / ratioPX).toFixed(PRV_DEC_PLACES);
    }

    /**
     * 将样式字符串化
     * @param {*} style 
     */
    var stringifyStyle = function(style){
        var stl = "";
        for(var i in style){
            stl += i + ":" + style[i] + ";";
        }
        return stl;
    }

    /**
     * 将htmls字符串化
     * @param {*} htmls html-json数组
     * @param {*} reverse 反转所有数组
     */
    var stringifyHtmlJson = function(htmls, reverse){
        var html = "";

        if(reverse){
            htmls.reverse();
        }

        for(var h of htmls){
            html += "<" + h.tagName;
            
            if(h.attributes){
                if(h.attributes.style){
                    html += " style='" + stringifyStyle(h.attributes.style) + "'";
                }

                if(h.attributes.props){
                    for(var name in h.attributes.props){
                        if(h.attributes.props[name] instanceof Object){
                            html += " " + name + "='" + JSON.stringify(h.attributes.props[name]) + "'";
                        }else{
                            html += " " + name + "='" + h.attributes.props[name] + "'";
                        }
                    }
                }
            }

            html += ">";

            if(h.text){
                html += h.text;
            }else if(h.children && h.children.length > 0){
                html += stringifyHtmlJson(h.children, reverse);
            }else if(h.innerHTML){
                html += h.innerHTML;
            }

            html += "</" + h.tagName + ">";
        }
        return html;
    }

    /**
     * 将样式驼峰名称转成分隔线命名
     * @param {*} name 
     * @returns 
     */
    var styleToDividerName = function(name){
        return name.replace(/([A-Z])/g, function(c){return "-" + c.toLowerCase()});
    }

    /**
     * 将本地json转成txt
     * @param {*} localJson 
     */
    this.convertToTxts = function(elementList){
        var texts = [];
        var el = null;
        for(var i = 0; i < elementList.length; i++){
            el = elementList[i];
            if(el.element._sType == "sText"){
                texts.push(el.element.innerText);
            }else if(el.element._sType == "sLayer"){
            }else if(el.element._sType == "sBreakline"){
            }else if(el.element._sType == "sParagraph"){
                texts.push("\r\n");
            }else if(el.element._sType == "sTableCell"){
            }
        }
        return texts;
    }
}
